/*!
 * # Semantic UI - Dropdown
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

(function($, window, document, undefined) {
  "use strict";

  window =
    typeof window != "undefined" && window.Math == Math
      ? window
      : typeof self != "undefined" && self.Math == Math
        ? self
        : Function("return this")();

  $.fn.dropdown = function(parameters) {
    var $allModules = $(this),
      $document = $(document),
      moduleSelector = $allModules.selector || "",
      hasTouch = "ontouchstart" in document.documentElement,
      time = new Date().getTime(),
      performance = [],
      query = arguments[0],
      methodInvoked = typeof query == "string",
      queryArguments = [].slice.call(arguments, 1),
      returnedValue;

    $allModules.each(function(elementIndex) {
      var settings = $.isPlainObject(parameters)
          ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
          : $.extend({}, $.fn.dropdown.settings),
        className = settings.className,
        message = settings.message,
        fields = settings.fields,
        keys = settings.keys,
        metadata = settings.metadata,
        namespace = settings.namespace,
        regExp = settings.regExp,
        selector = settings.selector,
        error = settings.error,
        templates = settings.templates,
        eventNamespace = "." + namespace,
        moduleNamespace = "module-" + namespace,
        $module = $(this),
        $context = $(settings.context),
        $text = $module.find(selector.text),
        $search = $module.find(selector.search),
        $sizer = $module.find(selector.sizer),
        $input = $module.find(selector.input),
        $icon = $module.find(selector.icon),
        $combo =
          $module.prev().find(selector.text).length > 0
            ? $module.prev().find(selector.text)
            : $module.prev(),
        $menu = $module.children(selector.menu),
        $item = $menu.find(selector.item),
        activated = false,
        itemActivated = false,
        internalChange = false,
        element = this,
        instance = $module.data(moduleNamespace),
        initialLoad,
        pageLostFocus,
        willRefocus,
        elementNamespace,
        id,
        selectObserver,
        menuObserver,
        module;

      module = {
        initialize: function() {
          module.debug("Initializing dropdown", settings);

          if (module.is.alreadySetup()) {
            module.setup.reference();
          } else {
            module.setup.layout();

            if (settings.values) {
              module.change.values(settings.values);
            }

            module.refreshData();

            module.save.defaults();
            module.restore.selected();

            module.create.id();
            module.bind.events();

            module.observeChanges();
            module.instantiate();
          }
        },

        instantiate: function() {
          module.verbose("Storing instance of dropdown", module);
          instance = module;
          $module.data(moduleNamespace, module);
        },

        destroy: function() {
          module.verbose("Destroying previous dropdown", $module);
          module.remove.tabbable();
          $module.off(eventNamespace).removeData(moduleNamespace);
          $menu.off(eventNamespace);
          $document.off(elementNamespace);
          module.disconnect.menuObserver();
          module.disconnect.selectObserver();
        },

        observeChanges: function() {
          if ("MutationObserver" in window) {
            selectObserver = new MutationObserver(module.event.select.mutation);
            menuObserver = new MutationObserver(module.event.menu.mutation);
            module.debug(
              "Setting up mutation observer",
              selectObserver,
              menuObserver
            );
            module.observe.select();
            module.observe.menu();
          }
        },

        disconnect: {
          menuObserver: function() {
            if (menuObserver) {
              menuObserver.disconnect();
            }
          },
          selectObserver: function() {
            if (selectObserver) {
              selectObserver.disconnect();
            }
          }
        },
        observe: {
          select: function() {
            if (module.has.input()) {
              selectObserver.observe($module[0], {
                childList: true,
                subtree: true
              });
            }
          },
          menu: function() {
            if (module.has.menu()) {
              menuObserver.observe($menu[0], {
                childList: true,
                subtree: true
              });
            }
          }
        },

        create: {
          id: function() {
            id = (Math.random().toString(16) + "000000000").substr(2, 8);
            elementNamespace = "." + id;
            module.verbose("Creating unique id for element", id);
          },
          userChoice: function(values) {
            var $userChoices, $userChoice, isUserValue, html;
            values = values || module.get.userValues();
            if (!values) {
              return false;
            }
            values = $.isArray(values) ? values : [values];
            $.each(values, function(index, value) {
              if (module.get.item(value) === false) {
                html = settings.templates.addition(
                  module.add.variables(message.addResult, value)
                );
                $userChoice = $("<div />")
                  .html(html)
                  .attr("data-" + metadata.value, value)
                  .attr("data-" + metadata.text, value)
                  .addClass(className.addition)
                  .addClass(className.item);
                if (settings.hideAdditions) {
                  $userChoice.addClass(className.hidden);
                }
                $userChoices =
                  $userChoices === undefined
                    ? $userChoice
                    : $userChoices.add($userChoice);
                module.verbose(
                  "Creating user choices for value",
                  value,
                  $userChoice
                );
              }
            });
            return $userChoices;
          },
          userLabels: function(value) {
            var userValues = module.get.userValues();
            if (userValues) {
              module.debug("Adding user labels", userValues);
              $.each(userValues, function(index, value) {
                module.verbose("Adding custom user value");
                module.add.label(value, value);
              });
            }
          },
          menu: function() {
            $menu = $("<div />")
              .addClass(className.menu)
              .appendTo($module);
          },
          sizer: function() {
            $sizer = $("<span />")
              .addClass(className.sizer)
              .insertAfter($search);
          }
        },

        search: function(query) {
          query = query !== undefined ? query : module.get.query();
          module.verbose("Searching for query", query);
          if (module.has.minCharacters(query)) {
            module.filter(query);
          } else {
            module.hide();
          }
        },

        select: {
          firstUnfiltered: function() {
            module.verbose("Selecting first non-filtered element");
            module.remove.selectedItem();
            $item
              .not(selector.unselectable)
              .not(selector.addition + selector.hidden)
              .eq(0)
              .addClass(className.selected);
          },
          nextAvailable: function($selected) {
            $selected = $selected.eq(0);
            var $nextAvailable = $selected
                .nextAll(selector.item)
                .not(selector.unselectable)
                .eq(0),
              $prevAvailable = $selected
                .prevAll(selector.item)
                .not(selector.unselectable)
                .eq(0),
              hasNext = $nextAvailable.length > 0;
            if (hasNext) {
              module.verbose("Moving selection to", $nextAvailable);
              $nextAvailable.addClass(className.selected);
            } else {
              module.verbose("Moving selection to", $prevAvailable);
              $prevAvailable.addClass(className.selected);
            }
          }
        },

        setup: {
          api: function() {
            var apiSettings = {
              debug: settings.debug,
              urlData: {
                value: module.get.value(),
                query: module.get.query()
              },
              on: false
            };
            module.verbose("First request, initializing API");
            $module.api(apiSettings);
          },
          layout: function() {
            if ($module.is("select")) {
              module.setup.select();
              module.setup.returnedObject();
            }
            if (!module.has.menu()) {
              module.create.menu();
            }
            if (module.is.search() && !module.has.search()) {
              module.verbose("Adding search input");
              $search = $("<input />")
                .addClass(className.search)
                .prop("autocomplete", "off")
                .insertBefore($text);
            }
            if (
              module.is.multiple() &&
              module.is.searchSelection() &&
              !module.has.sizer()
            ) {
              module.create.sizer();
            }
            if (settings.allowTab) {
              module.set.tabbable();
            }
          },
          select: function() {
            var selectValues = module.get.selectValues();
            module.debug("Dropdown initialized on a select", selectValues);
            if ($module.is("select")) {
              $input = $module;
            }
            // see if select is placed correctly already
            if ($input.parent(selector.dropdown).length > 0) {
              module.debug(
                "UI dropdown already exists. Creating dropdown menu only"
              );
              $module = $input.closest(selector.dropdown);
              if (!module.has.menu()) {
                module.create.menu();
              }
              $menu = $module.children(selector.menu);
              module.setup.menu(selectValues);
            } else {
              module.debug("Creating entire dropdown from select");
              $module = $("<div />")
                .attr("class", $input.attr("class"))
                .addClass(className.selection)
                .addClass(className.dropdown)
                .html(templates.dropdown(selectValues))
                .insertBefore($input);
              if (
                $input.hasClass(className.multiple) &&
                $input.prop("multiple") === false
              ) {
                module.error(error.missingMultiple);
                $input.prop("multiple", true);
              }
              if ($input.is("[multiple]")) {
                module.set.multiple();
              }
              if ($input.prop("disabled")) {
                module.debug("Disabling dropdown");
                $module.addClass(className.disabled);
              }
              $input
                .removeAttr("class")
                .detach()
                .prependTo($module);
            }
            module.refresh();
          },
          menu: function(values) {
            $menu.html(templates.menu(values, fields));
            $item = $menu.find(selector.item);
          },
          reference: function() {
            module.debug(
              "Dropdown behavior was called on select, replacing with closest dropdown"
            );
            // replace module reference
            $module = $module.parent(selector.dropdown);
            instance = $module.data(moduleNamespace);
            element = $module.get(0);
            module.refresh();
            module.setup.returnedObject();
          },
          returnedObject: function() {
            var $firstModules = $allModules.slice(0, elementIndex),
              $lastModules = $allModules.slice(elementIndex + 1);
            // adjust all modules to use correct reference
            $allModules = $firstModules.add($module).add($lastModules);
          }
        },

        refresh: function() {
          module.refreshSelectors();
          module.refreshData();
        },

        refreshItems: function() {
          $item = $menu.find(selector.item);
        },

        refreshSelectors: function() {
          module.verbose("Refreshing selector cache");
          $text = $module.find(selector.text);
          $search = $module.find(selector.search);
          $input = $module.find(selector.input);
          $icon = $module.find(selector.icon);
          $combo =
            $module.prev().find(selector.text).length > 0
              ? $module.prev().find(selector.text)
              : $module.prev();
          $menu = $module.children(selector.menu);
          $item = $menu.find(selector.item);
        },

        refreshData: function() {
          module.verbose("Refreshing cached metadata");
          $item.removeData(metadata.text).removeData(metadata.value);
        },

        clearData: function() {
          module.verbose("Clearing metadata");
          $item.removeData(metadata.text).removeData(metadata.value);
          $module
            .removeData(metadata.defaultText)
            .removeData(metadata.defaultValue)
            .removeData(metadata.placeholderText);
        },

        toggle: function() {
          module.verbose("Toggling menu visibility");
          if (!module.is.active()) {
            module.show();
          } else {
            module.hide();
          }
        },

        show: function(callback) {
          callback = $.isFunction(callback) ? callback : function() {};
          if (!module.can.show() && module.is.remote()) {
            module.debug("No API results retrieved, searching before show");
            module.queryRemote(module.get.query(), module.show);
          }
          if (module.can.show() && !module.is.active()) {
            module.debug("Showing dropdown");
            if (
              module.has.message() &&
              !(module.has.maxSelections() || module.has.allResultsFiltered())
            ) {
              module.remove.message();
            }
            if (module.is.allFiltered()) {
              return true;
            }
            if (settings.onShow.call(element) !== false) {
              module.animate.show(function() {
                if (module.can.click()) {
                  module.bind.intent();
                }
                if (module.has.menuSearch()) {
                  module.focusSearch();
                }
                module.set.visible();
                callback.call(element);
              });
            }
          }
        },

        hide: function(callback) {
          callback = $.isFunction(callback) ? callback : function() {};
          if (module.is.active() && !module.is.animatingOutward()) {
            module.debug("Hiding dropdown");
            if (settings.onHide.call(element) !== false) {
              module.animate.hide(function() {
                module.remove.visible();
                callback.call(element);
              });
            }
          }
        },

        hideOthers: function() {
          module.verbose("Finding other dropdowns to hide");
          $allModules
            .not($module)
            .has(selector.menu + "." + className.visible)
            .dropdown("hide");
        },

        hideMenu: function() {
          module.verbose("Hiding menu  instantaneously");
          module.remove.active();
          module.remove.visible();
          $menu.transition("hide");
        },

        hideSubMenus: function() {
          var $subMenus = $menu.children(selector.item).find(selector.menu);
          module.verbose("Hiding sub menus", $subMenus);
          $subMenus.transition("hide");
        },

        bind: {
          events: function() {
            if (hasTouch) {
              module.bind.touchEvents();
            }
            module.bind.keyboardEvents();
            module.bind.inputEvents();
            module.bind.mouseEvents();
          },
          touchEvents: function() {
            module.debug(
              "Touch device detected binding additional touch events"
            );
            if (module.is.searchSelection()) {
              // do nothing special yet
            } else if (module.is.single()) {
              $module.on(
                "touchstart" + eventNamespace,
                module.event.test.toggle
              );
            }
            $menu.on(
              "touchstart" + eventNamespace,
              selector.item,
              module.event.item.mouseenter
            );
          },
          keyboardEvents: function() {
            module.verbose("Binding keyboard events");
            $module.on("keydown" + eventNamespace, module.event.keydown);
            if (module.has.search()) {
              $module.on(
                module.get.inputEvent() + eventNamespace,
                selector.search,
                module.event.input
              );
            }
            if (module.is.multiple()) {
              $document.on(
                "keydown" + elementNamespace,
                module.event.document.keydown
              );
            }
          },
          inputEvents: function() {
            module.verbose("Binding input change events");
            $module.on(
              "change" + eventNamespace,
              selector.input,
              module.event.change
            );
          },
          mouseEvents: function() {
            module.verbose("Binding mouse events");
            if (module.is.multiple()) {
              $module
                .on(
                  "click" + eventNamespace,
                  selector.label,
                  module.event.label.click
                )
                .on(
                  "click" + eventNamespace,
                  selector.remove,
                  module.event.remove.click
                );
            }
            if (module.is.searchSelection()) {
              $module
                .on("mousedown" + eventNamespace, module.event.mousedown)
                .on("mouseup" + eventNamespace, module.event.mouseup)
                .on(
                  "mousedown" + eventNamespace,
                  selector.menu,
                  module.event.menu.mousedown
                )
                .on(
                  "mouseup" + eventNamespace,
                  selector.menu,
                  module.event.menu.mouseup
                )
                .on(
                  "click" + eventNamespace,
                  selector.icon,
                  module.event.icon.click
                )
                .on(
                  "focus" + eventNamespace,
                  selector.search,
                  module.event.search.focus
                )
                .on(
                  "click" + eventNamespace,
                  selector.search,
                  module.event.search.focus
                )
                .on(
                  "blur" + eventNamespace,
                  selector.search,
                  module.event.search.blur
                )
                .on(
                  "click" + eventNamespace,
                  selector.text,
                  module.event.text.focus
                );
              if (module.is.multiple()) {
                $module.on("click" + eventNamespace, module.event.click);
              }
            } else {
              if (settings.on == "click") {
                $module
                  .on(
                    "click" + eventNamespace,
                    selector.icon,
                    module.event.icon.click
                  )
                  .on("click" + eventNamespace, module.event.test.toggle);
              } else if (settings.on == "hover") {
                $module
                  .on("mouseenter" + eventNamespace, module.delay.show)
                  .on("mouseleave" + eventNamespace, module.delay.hide);
              } else {
                $module.on(settings.on + eventNamespace, module.toggle);
              }
              $module
                .on("mousedown" + eventNamespace, module.event.mousedown)
                .on("mouseup" + eventNamespace, module.event.mouseup)
                .on("focus" + eventNamespace, module.event.focus);
              if (module.has.menuSearch()) {
                $module.on(
                  "blur" + eventNamespace,
                  selector.search,
                  module.event.search.blur
                );
              } else {
                $module.on("blur" + eventNamespace, module.event.blur);
              }
            }
            $menu
              .on(
                "mouseenter" + eventNamespace,
                selector.item,
                module.event.item.mouseenter
              )
              .on(
                "mouseleave" + eventNamespace,
                selector.item,
                module.event.item.mouseleave
              )
              .on(
                "click" + eventNamespace,
                selector.item,
                module.event.item.click
              );
          },
          intent: function() {
            module.verbose("Binding hide intent event to document");
            if (hasTouch) {
              $document
                .on("touchstart" + elementNamespace, module.event.test.touch)
                .on("touchmove" + elementNamespace, module.event.test.touch);
            }
            $document.on("click" + elementNamespace, module.event.test.hide);
          }
        },

        unbind: {
          intent: function() {
            module.verbose("Removing hide intent event from document");
            if (hasTouch) {
              $document
                .off("touchstart" + elementNamespace)
                .off("touchmove" + elementNamespace);
            }
            $document.off("click" + elementNamespace);
          }
        },

        filter: function(query) {
          var searchTerm = query !== undefined ? query : module.get.query(),
            afterFiltered = function() {
              if (module.is.multiple()) {
                module.filterActive();
              }
              if (query || (!query && module.get.activeItem().length == 0)) {
                module.select.firstUnfiltered();
              }
              if (module.has.allResultsFiltered()) {
                if (settings.onNoResults.call(element, searchTerm)) {
                  if (settings.allowAdditions) {
                    if (settings.hideAdditions) {
                      module.verbose(
                        "User addition with no menu, setting empty style"
                      );
                      module.set.empty();
                      module.hideMenu();
                    }
                  } else {
                    module.verbose(
                      "All items filtered, showing message",
                      searchTerm
                    );
                    module.add.message(message.noResults);
                  }
                } else {
                  module.verbose(
                    "All items filtered, hiding dropdown",
                    searchTerm
                  );
                  module.hideMenu();
                }
              } else {
                module.remove.empty();
                module.remove.message();
              }
              if (settings.allowAdditions) {
                module.add.userSuggestion(query);
              }
              if (
                module.is.searchSelection() &&
                module.can.show() &&
                module.is.focusedOnSearch()
              ) {
                module.show();
              }
            };
          if (settings.useLabels && module.has.maxSelections()) {
            return;
          }
          if (settings.apiSettings) {
            if (module.can.useAPI()) {
              module.queryRemote(searchTerm, function() {
                if (settings.filterRemoteData) {
                  module.filterItems(searchTerm);
                }
                afterFiltered();
              });
            } else {
              module.error(error.noAPI);
            }
          } else {
            module.filterItems(searchTerm);
            afterFiltered();
          }
        },

        queryRemote: function(query, callback) {
          var apiSettings = {
            errorDuration: false,
            cache: "local",
            throttle: settings.throttle,
            urlData: {
              query: query
            },
            onError: function() {
              module.add.message(message.serverError);
              callback();
            },
            onFailure: function() {
              module.add.message(message.serverError);
              callback();
            },
            onSuccess: function(response) {
              var values = response[fields.remoteValues],
                hasRemoteValues = $.isArray(values) && values.length > 0;
              if (hasRemoteValues) {
                module.remove.message();
                module.setup.menu({
                  values: response[fields.remoteValues]
                });
              } else {
                module.add.message(message.noResults);
              }
              callback();
            }
          };
          if (!$module.api("get request")) {
            module.setup.api();
          }
          apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
          $module.api("setting", apiSettings).api("query");
        },

        filterItems: function(query) {
          var searchTerm = query !== undefined ? query : module.get.query(),
            results = null,
            escapedTerm = module.escape.string(searchTerm),
            beginsWithRegExp = new RegExp("^" + escapedTerm, "igm");
          // avoid loop if we're matching nothing
          if (module.has.query()) {
            results = [];

            module.verbose("Searching for matching values", searchTerm);
            $item.each(function() {
              var $choice = $(this),
                text,
                value;
              if (settings.match == "both" || settings.match == "text") {
                text = String(module.get.choiceText($choice, false));
                if (text.search(beginsWithRegExp) !== -1) {
                  results.push(this);
                  return true;
                } else if (
                  settings.fullTextSearch === "exact" &&
                  module.exactSearch(searchTerm, text)
                ) {
                  results.push(this);
                  return true;
                } else if (
                  settings.fullTextSearch === true &&
                  module.fuzzySearch(searchTerm, text)
                ) {
                  results.push(this);
                  return true;
                }
              }
              if (settings.match == "both" || settings.match == "value") {
                value = String(module.get.choiceValue($choice, text));
                if (value.search(beginsWithRegExp) !== -1) {
                  results.push(this);
                  return true;
                } else if (
                  settings.fullTextSearch === "exact" &&
                  module.exactSearch(searchTerm, value)
                ) {
                  results.push(this);
                  return true;
                } else if (
                  settings.fullTextSearch === true &&
                  module.fuzzySearch(searchTerm, value)
                ) {
                  results.push(this);
                  return true;
                }
              }
            });
          }
          module.debug("Showing only matched items", searchTerm);
          module.remove.filteredItem();
          if (results) {
            $item.not(results).addClass(className.filtered);
          }
        },

        fuzzySearch: function(query, term) {
          var termLength = term.length,
            queryLength = query.length;
          query = query.toLowerCase();
          term = term.toLowerCase();
          if (queryLength > termLength) {
            return false;
          }
          if (queryLength === termLength) {
            return query === term;
          }
          search: for (
            var characterIndex = 0, nextCharacterIndex = 0;
            characterIndex < queryLength;
            characterIndex++
          ) {
            var queryCharacter = query.charCodeAt(characterIndex);
            while (nextCharacterIndex < termLength) {
              if (term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
                continue search;
              }
            }
            return false;
          }
          return true;
        },
        exactSearch: function(query, term) {
          query = query.toLowerCase();
          term = term.toLowerCase();
          if (term.indexOf(query) > -1) {
            return true;
          }
          return false;
        },
        filterActive: function() {
          if (settings.useLabels) {
            $item.filter("." + className.active).addClass(className.filtered);
          }
        },

        focusSearch: function(skipHandler) {
          if (module.has.search() && !module.is.focusedOnSearch()) {
            if (skipHandler) {
              $module.off("focus" + eventNamespace, selector.search);
              $search.focus();
              $module.on(
                "focus" + eventNamespace,
                selector.search,
                module.event.search.focus
              );
            } else {
              $search.focus();
            }
          }
        },

        forceSelection: function() {
          var $currentlySelected = $item
              .not(className.filtered)
              .filter("." + className.selected)
              .eq(0),
            $activeItem = $item
              .not(className.filtered)
              .filter("." + className.active)
              .eq(0),
            $selectedItem =
              $currentlySelected.length > 0 ? $currentlySelected : $activeItem,
            hasSelected = $selectedItem.length > 0;
          if (hasSelected && !module.is.multiple()) {
            module.debug(
              "Forcing partial selection to selected item",
              $selectedItem
            );
            module.event.item.click.call($selectedItem, {}, true);
            return;
          } else {
            if (settings.allowAdditions) {
              module.set.selected(module.get.query());
              module.remove.searchTerm();
            } else {
              module.remove.searchTerm();
            }
          }
        },

        change: {
          values: function(values) {
            if (!settings.allowAdditions) {
              module.clear();
            }
            module.debug("Creating dropdown with specified values", values);
            module.setup.menu({ values: values });
            $.each(values, function(index, item) {
              if (item.selected == true) {
                module.debug("Setting initial selection to", item.value);
                module.set.selected(item.value);
                return true;
              }
            });
          }
        },

        event: {
          change: function() {
            if (!internalChange) {
              module.debug("Input changed, updating selection");
              module.set.selected();
            }
          },
          focus: function() {
            if (
              settings.showOnFocus &&
              !activated &&
              module.is.hidden() &&
              !pageLostFocus
            ) {
              module.show();
            }
          },
          blur: function(event) {
            pageLostFocus = document.activeElement === this;
            if (!activated && !pageLostFocus) {
              module.remove.activeLabel();
              module.hide();
            }
          },
          mousedown: function() {
            if (module.is.searchSelection()) {
              // prevent menu hiding on immediate re-focus
              willRefocus = true;
            } else {
              // prevents focus callback from occurring on mousedown
              activated = true;
            }
          },
          mouseup: function() {
            if (module.is.searchSelection()) {
              // prevent menu hiding on immediate re-focus
              willRefocus = false;
            } else {
              activated = false;
            }
          },
          click: function(event) {
            var $target = $(event.target);
            // focus search
            if ($target.is($module)) {
              if (!module.is.focusedOnSearch()) {
                module.focusSearch();
              } else {
                module.show();
              }
            }
          },
          search: {
            focus: function() {
              activated = true;
              if (module.is.multiple()) {
                module.remove.activeLabel();
              }
              if (settings.showOnFocus) {
                module.search();
              }
            },
            blur: function(event) {
              pageLostFocus = document.activeElement === this;
              if (module.is.searchSelection() && !willRefocus) {
                if (!itemActivated && !pageLostFocus) {
                  if (settings.forceSelection) {
                    module.forceSelection();
                  }
                  module.hide();
                }
              }
              willRefocus = false;
            }
          },
          icon: {
            click: function(event) {
              module.toggle();
            }
          },
          text: {
            focus: function(event) {
              activated = true;
              module.focusSearch();
            }
          },
          input: function(event) {
            if (module.is.multiple() || module.is.searchSelection()) {
              module.set.filtered();
            }
            clearTimeout(module.timer);
            module.timer = setTimeout(module.search, settings.delay.search);
          },
          label: {
            click: function(event) {
              var $label = $(this),
                $labels = $module.find(selector.label),
                $activeLabels = $labels.filter("." + className.active),
                $nextActive = $label.nextAll("." + className.active),
                $prevActive = $label.prevAll("." + className.active),
                $range =
                  $nextActive.length > 0
                    ? $label
                        .nextUntil($nextActive)
                        .add($activeLabels)
                        .add($label)
                    : $label
                        .prevUntil($prevActive)
                        .add($activeLabels)
                        .add($label);
              if (event.shiftKey) {
                $activeLabels.removeClass(className.active);
                $range.addClass(className.active);
              } else if (event.ctrlKey) {
                $label.toggleClass(className.active);
              } else {
                $activeLabels.removeClass(className.active);
                $label.addClass(className.active);
              }
              settings.onLabelSelect.apply(
                this,
                $labels.filter("." + className.active)
              );
            }
          },
          remove: {
            click: function() {
              var $label = $(this).parent();
              if ($label.hasClass(className.active)) {
                // remove all selected labels
                module.remove.activeLabels();
              } else {
                // remove this label only
                module.remove.activeLabels($label);
              }
            }
          },
          test: {
            toggle: function(event) {
              var toggleBehavior = module.is.multiple()
                ? module.show
                : module.toggle;
              if (
                module.is.bubbledLabelClick(event) ||
                module.is.bubbledIconClick(event)
              ) {
                return;
              }
              if (module.determine.eventOnElement(event, toggleBehavior)) {
                event.preventDefault();
              }
            },
            touch: function(event) {
              module.determine.eventOnElement(event, function() {
                if (event.type == "touchstart") {
                  module.timer = setTimeout(function() {
                    module.hide();
                  }, settings.delay.touch);
                } else if (event.type == "touchmove") {
                  clearTimeout(module.timer);
                }
              });
              event.stopPropagation();
            },
            hide: function(event) {
              module.determine.eventInModule(event, module.hide);
            }
          },
          select: {
            mutation: function(mutations) {
              module.debug("<select> modified, recreating menu");
              var isSelectMutation = false;
              $.each(mutations, function(index, mutation) {
                if (
                  $(mutation.target).is("select") ||
                  $(mutation.addedNodes).is("select")
                ) {
                  isSelectMutation = true;
                  return true;
                }
              });
              if (isSelectMutation) {
                module.disconnect.selectObserver();
                module.refresh();
                module.setup.select();
                module.set.selected();
                module.observe.select();
              }
            }
          },
          menu: {
            mutation: function(mutations) {
              var mutation = mutations[0],
                $addedNode = mutation.addedNodes
                  ? $(mutation.addedNodes[0])
                  : $(false),
                $removedNode = mutation.removedNodes
                  ? $(mutation.removedNodes[0])
                  : $(false),
                $changedNodes = $addedNode.add($removedNode),
                isUserAddition =
                  $changedNodes.is(selector.addition) ||
                  $changedNodes.closest(selector.addition).length > 0,
                isMessage =
                  $changedNodes.is(selector.message) ||
                  $changedNodes.closest(selector.message).length > 0;
              if (isUserAddition || isMessage) {
                module.debug("Updating item selector cache");
                module.refreshItems();
              } else {
                module.debug("Menu modified, updating selector cache");
                module.refresh();
              }
            },
            mousedown: function() {
              itemActivated = true;
            },
            mouseup: function() {
              itemActivated = false;
            }
          },
          item: {
            mouseenter: function(event) {
              var $target = $(event.target),
                $item = $(this),
                $subMenu = $item.children(selector.menu),
                $otherMenus = $item
                  .siblings(selector.item)
                  .children(selector.menu),
                hasSubMenu = $subMenu.length > 0,
                isBubbledEvent = $subMenu.find($target).length > 0;
              if (!isBubbledEvent && hasSubMenu) {
                clearTimeout(module.itemTimer);
                module.itemTimer = setTimeout(function() {
                  module.verbose("Showing sub-menu", $subMenu);
                  $.each($otherMenus, function() {
                    module.animate.hide(false, $(this));
                  });
                  module.animate.show(false, $subMenu);
                }, settings.delay.show);
                event.preventDefault();
              }
            },
            mouseleave: function(event) {
              var $subMenu = $(this).children(selector.menu);
              if ($subMenu.length > 0) {
                clearTimeout(module.itemTimer);
                module.itemTimer = setTimeout(function() {
                  module.verbose("Hiding sub-menu", $subMenu);
                  module.animate.hide(false, $subMenu);
                }, settings.delay.hide);
              }
            },
            click: function(event, skipRefocus) {
              var $choice = $(this),
                $target = event ? $(event.target) : $(""),
                $subMenu = $choice.find(selector.menu),
                text = module.get.choiceText($choice),
                value = module.get.choiceValue($choice, text),
                hasSubMenu = $subMenu.length > 0,
                isBubbledEvent = $subMenu.find($target).length > 0;
              // prevents IE11 bug where menu receives focus even though `tabindex=-1`
              if (module.has.menuSearch()) {
                $(document.activeElement).blur();
              }
              if (
                !isBubbledEvent &&
                (!hasSubMenu || settings.allowCategorySelection)
              ) {
                if (module.is.searchSelection()) {
                  if (settings.allowAdditions) {
                    module.remove.userAddition();
                  }
                  module.remove.searchTerm();
                  if (!module.is.focusedOnSearch() && !(skipRefocus == true)) {
                    module.focusSearch(true);
                  }
                }
                if (!settings.useLabels) {
                  module.remove.filteredItem();
                  module.set.scrollPosition($choice);
                }
                module.determine.selectAction.call(this, text, value);
              }
            }
          },

          document: {
            // label selection should occur even when element has no focus
            keydown: function(event) {
              var pressedKey = event.which,
                isShortcutKey = module.is.inObject(pressedKey, keys);
              if (isShortcutKey) {
                var $label = $module.find(selector.label),
                  $activeLabel = $label.filter("." + className.active),
                  activeValue = $activeLabel.data(metadata.value),
                  labelIndex = $label.index($activeLabel),
                  labelCount = $label.length,
                  hasActiveLabel = $activeLabel.length > 0,
                  hasMultipleActive = $activeLabel.length > 1,
                  isFirstLabel = labelIndex === 0,
                  isLastLabel = labelIndex + 1 == labelCount,
                  isSearch = module.is.searchSelection(),
                  isFocusedOnSearch = module.is.focusedOnSearch(),
                  isFocused = module.is.focused(),
                  caretAtStart =
                    isFocusedOnSearch && module.get.caretPosition() === 0,
                  $nextLabel;
                if (isSearch && !hasActiveLabel && !isFocusedOnSearch) {
                  return;
                }

                if (pressedKey == keys.leftArrow) {
                  // activate previous label
                  if ((isFocused || caretAtStart) && !hasActiveLabel) {
                    module.verbose("Selecting previous label");
                    $label.last().addClass(className.active);
                  } else if (hasActiveLabel) {
                    if (!event.shiftKey) {
                      module.verbose("Selecting previous label");
                      $label.removeClass(className.active);
                    } else {
                      module.verbose("Adding previous label to selection");
                    }
                    if (isFirstLabel && !hasMultipleActive) {
                      $activeLabel.addClass(className.active);
                    } else {
                      $activeLabel
                        .prev(selector.siblingLabel)
                        .addClass(className.active)
                        .end();
                    }
                    event.preventDefault();
                  }
                } else if (pressedKey == keys.rightArrow) {
                  // activate first label
                  if (isFocused && !hasActiveLabel) {
                    $label.first().addClass(className.active);
                  }
                  // activate next label
                  if (hasActiveLabel) {
                    if (!event.shiftKey) {
                      module.verbose("Selecting next label");
                      $label.removeClass(className.active);
                    } else {
                      module.verbose("Adding next label to selection");
                    }
                    if (isLastLabel) {
                      if (isSearch) {
                        if (!isFocusedOnSearch) {
                          module.focusSearch();
                        } else {
                          $label.removeClass(className.active);
                        }
                      } else if (hasMultipleActive) {
                        $activeLabel
                          .next(selector.siblingLabel)
                          .addClass(className.active);
                      } else {
                        $activeLabel.addClass(className.active);
                      }
                    } else {
                      $activeLabel
                        .next(selector.siblingLabel)
                        .addClass(className.active);
                    }
                    event.preventDefault();
                  }
                } else if (
                  pressedKey == keys.deleteKey ||
                  pressedKey == keys.backspace
                ) {
                  if (hasActiveLabel) {
                    module.verbose("Removing active labels");
                    if (isLastLabel) {
                      if (isSearch && !isFocusedOnSearch) {
                        module.focusSearch();
                      }
                    }
                    $activeLabel
                      .last()
                      .next(selector.siblingLabel)
                      .addClass(className.active);
                    module.remove.activeLabels($activeLabel);
                    event.preventDefault();
                  } else if (
                    caretAtStart &&
                    !hasActiveLabel &&
                    pressedKey == keys.backspace
                  ) {
                    module.verbose("Removing last label on input backspace");
                    $activeLabel = $label.last().addClass(className.active);
                    module.remove.activeLabels($activeLabel);
                  }
                } else {
                  $activeLabel.removeClass(className.active);
                }
              }
            }
          },

          keydown: function(event) {
            var pressedKey = event.which,
              isShortcutKey = module.is.inObject(pressedKey, keys);
            if (isShortcutKey) {
              var $currentlySelected = $item
                  .not(selector.unselectable)
                  .filter("." + className.selected)
                  .eq(0),
                $activeItem = $menu.children("." + className.active).eq(0),
                $selectedItem =
                  $currentlySelected.length > 0
                    ? $currentlySelected
                    : $activeItem,
                $visibleItems =
                  $selectedItem.length > 0
                    ? $selectedItem
                        .siblings(":not(." + className.filtered + ")")
                        .addBack()
                    : $menu.children(":not(." + className.filtered + ")"),
                $subMenu = $selectedItem.children(selector.menu),
                $parentMenu = $selectedItem.closest(selector.menu),
                inVisibleMenu =
                  $parentMenu.hasClass(className.visible) ||
                  $parentMenu.hasClass(className.animating) ||
                  $parentMenu.parent(selector.menu).length > 0,
                hasSubMenu = $subMenu.length > 0,
                hasSelectedItem = $selectedItem.length > 0,
                selectedIsSelectable =
                  $selectedItem.not(selector.unselectable).length > 0,
                delimiterPressed =
                  pressedKey == keys.delimiter &&
                  settings.allowAdditions &&
                  module.is.multiple(),
                isAdditionWithoutMenu =
                  settings.allowAdditions &&
                  settings.hideAdditions &&
                  (pressedKey == keys.enter || delimiterPressed) &&
                  selectedIsSelectable,
                $nextItem,
                isSubMenuItem,
                newIndex;
              // allow selection with menu closed
              if (isAdditionWithoutMenu) {
                module.verbose(
                  "Selecting item from keyboard shortcut",
                  $selectedItem
                );
                module.event.item.click.call($selectedItem, event);
                if (module.is.searchSelection()) {
                  module.remove.searchTerm();
                }
              }

              // visible menu keyboard shortcuts
              if (module.is.visible()) {
                // enter (select or open sub-menu)
                if (pressedKey == keys.enter || delimiterPressed) {
                  if (
                    pressedKey == keys.enter &&
                    hasSelectedItem &&
                    hasSubMenu &&
                    !settings.allowCategorySelection
                  ) {
                    module.verbose(
                      "Pressed enter on unselectable category, opening sub menu"
                    );
                    pressedKey = keys.rightArrow;
                  } else if (selectedIsSelectable) {
                    module.verbose(
                      "Selecting item from keyboard shortcut",
                      $selectedItem
                    );
                    module.event.item.click.call($selectedItem, event);
                    if (module.is.searchSelection()) {
                      module.remove.searchTerm();
                    }
                  }
                  event.preventDefault();
                }

                // sub-menu actions
                if (hasSelectedItem) {
                  if (pressedKey == keys.leftArrow) {
                    isSubMenuItem = $parentMenu[0] !== $menu[0];

                    if (isSubMenuItem) {
                      module.verbose("Left key pressed, closing sub-menu");
                      module.animate.hide(false, $parentMenu);
                      $selectedItem.removeClass(className.selected);
                      $parentMenu
                        .closest(selector.item)
                        .addClass(className.selected);
                      event.preventDefault();
                    }
                  }

                  // right arrow (show sub-menu)
                  if (pressedKey == keys.rightArrow) {
                    if (hasSubMenu) {
                      module.verbose("Right key pressed, opening sub-menu");
                      module.animate.show(false, $subMenu);
                      $selectedItem.removeClass(className.selected);
                      $subMenu
                        .find(selector.item)
                        .eq(0)
                        .addClass(className.selected);
                      event.preventDefault();
                    }
                  }
                }

                // up arrow (traverse menu up)
                if (pressedKey == keys.upArrow) {
                  $nextItem =
                    hasSelectedItem && inVisibleMenu
                      ? $selectedItem
                          .prevAll(
                            selector.item +
                              ":not(" +
                              selector.unselectable +
                              ")"
                          )
                          .eq(0)
                      : $item.eq(0);
                  if ($visibleItems.index($nextItem) < 0) {
                    module.verbose(
                      "Up key pressed but reached top of current menu"
                    );
                    event.preventDefault();
                    return;
                  } else {
                    module.verbose("Up key pressed, changing active item");
                    $selectedItem.removeClass(className.selected);
                    $nextItem.addClass(className.selected);
                    module.set.scrollPosition($nextItem);
                    if (settings.selectOnKeydown && module.is.single()) {
                      module.set.selectedItem($nextItem);
                    }
                  }
                  event.preventDefault();
                }

                // down arrow (traverse menu down)
                if (pressedKey == keys.downArrow) {
                  $nextItem =
                    hasSelectedItem && inVisibleMenu
                      ? ($nextItem = $selectedItem
                          .nextAll(
                            selector.item +
                              ":not(" +
                              selector.unselectable +
                              ")"
                          )
                          .eq(0))
                      : $item.eq(0);
                  if ($nextItem.length === 0) {
                    module.verbose(
                      "Down key pressed but reached bottom of current menu"
                    );
                    event.preventDefault();
                    return;
                  } else {
                    module.verbose("Down key pressed, changing active item");
                    $item.removeClass(className.selected);
                    $nextItem.addClass(className.selected);
                    module.set.scrollPosition($nextItem);
                    if (settings.selectOnKeydown && module.is.single()) {
                      module.set.selectedItem($nextItem);
                    }
                  }
                  event.preventDefault();
                }

                // page down (show next page)
                if (pressedKey == keys.pageUp) {
                  module.scrollPage("up");
                  event.preventDefault();
                }
                if (pressedKey == keys.pageDown) {
                  module.scrollPage("down");
                  event.preventDefault();
                }

                // escape (close menu)
                if (pressedKey == keys.escape) {
                  module.verbose("Escape key pressed, closing dropdown");
                  module.hide();
                }
              } else {
                // delimiter key
                if (delimiterPressed) {
                  event.preventDefault();
                }
                // down arrow (open menu)
                if (pressedKey == keys.downArrow && !module.is.visible()) {
                  module.verbose("Down key pressed, showing dropdown");
                  module.show();
                  event.preventDefault();
                }
              }
            } else {
              if (!module.has.search()) {
                module.set.selectedLetter(String.fromCharCode(pressedKey));
              }
            }
          }
        },

        trigger: {
          change: function() {
            var events = document.createEvent("HTMLEvents"),
              inputElement = $input[0];
            if (inputElement) {
              module.verbose("Triggering native change event");
              events.initEvent("change", true, false);
              inputElement.dispatchEvent(events);
            }
          }
        },

        determine: {
          selectAction: function(text, value) {
            module.verbose("Determining action", settings.action);
            if ($.isFunction(module.action[settings.action])) {
              module.verbose(
                "Triggering preset action",
                settings.action,
                text,
                value
              );
              module.action[settings.action].call(element, text, value, this);
            } else if ($.isFunction(settings.action)) {
              module.verbose(
                "Triggering user action",
                settings.action,
                text,
                value
              );
              settings.action.call(element, text, value, this);
            } else {
              module.error(error.action, settings.action);
            }
          },
          eventInModule: function(event, callback) {
            var $target = $(event.target),
              inDocument = $target.closest(document.documentElement).length > 0,
              inModule = $target.closest($module).length > 0;
            callback = $.isFunction(callback) ? callback : function() {};
            if (inDocument && !inModule) {
              module.verbose("Triggering event", callback);
              callback();
              return true;
            } else {
              module.verbose("Event occurred in dropdown, canceling callback");
              return false;
            }
          },
          eventOnElement: function(event, callback) {
            var $target = $(event.target),
              $label = $target.closest(selector.siblingLabel),
              inVisibleDOM = document.body.contains(event.target),
              notOnLabel = $module.find($label).length === 0,
              notInMenu = $target.closest($menu).length === 0;
            callback = $.isFunction(callback) ? callback : function() {};
            if (inVisibleDOM && notOnLabel && notInMenu) {
              module.verbose("Triggering event", callback);
              callback();
              return true;
            } else {
              module.verbose(
                "Event occurred in dropdown menu, canceling callback"
              );
              return false;
            }
          }
        },

        action: {
          nothing: function() {},

          activate: function(text, value, element) {
            value = value !== undefined ? value : text;
            if (module.can.activate($(element))) {
              module.set.selected(value, $(element));
              if (module.is.multiple() && !module.is.allFiltered()) {
                return;
              } else {
                module.hideAndClear();
              }
            }
          },

          select: function(text, value, element) {
            value = value !== undefined ? value : text;
            if (module.can.activate($(element))) {
              module.set.value(value, text, $(element));
              if (module.is.multiple() && !module.is.allFiltered()) {
                return;
              } else {
                module.hideAndClear();
              }
            }
          },

          combo: function(text, value, element) {
            value = value !== undefined ? value : text;
            module.set.selected(value, $(element));
            module.hideAndClear();
          },

          hide: function(text, value, element) {
            module.set.value(value, text);
            module.hideAndClear();
          }
        },

        get: {
          id: function() {
            return id;
          },
          defaultText: function() {
            return $module.data(metadata.defaultText);
          },
          defaultValue: function() {
            return $module.data(metadata.defaultValue);
          },
          placeholderText: function() {
            if (
              settings.placeholder != "auto" &&
              typeof settings.placeholder == "string"
            ) {
              return settings.placeholder;
            }
            return $module.data(metadata.placeholderText) || "";
          },
          text: function() {
            return $text.text();
          },
          query: function() {
            return $.trim($search.val());
          },
          searchWidth: function(value) {
            value = value !== undefined ? value : $search.val();
            $sizer.text(value);
            // prevent rounding issues
            return Math.ceil($sizer.width() + 1);
          },
          selectionCount: function() {
            var values = module.get.values(),
              count;
            count = module.is.multiple()
              ? $.isArray(values)
                ? values.length
                : 0
              : module.get.value() !== ""
                ? 1
                : 0;
            return count;
          },
          transition: function($subMenu) {
            return settings.transition == "auto"
              ? module.is.upward($subMenu)
                ? "slide up"
                : "slide down"
              : settings.transition;
          },
          userValues: function() {
            var values = module.get.values();
            if (!values) {
              return false;
            }
            values = $.isArray(values) ? values : [values];
            return $.grep(values, function(value) {
              return module.get.item(value) === false;
            });
          },
          uniqueArray: function(array) {
            return $.grep(array, function(value, index) {
              return $.inArray(value, array) === index;
            });
          },
          caretPosition: function() {
            var input = $search.get(0),
              range,
              rangeLength;
            if ("selectionStart" in input) {
              return input.selectionStart;
            } else if (document.selection) {
              input.focus();
              range = document.selection.createRange();
              rangeLength = range.text.length;
              range.moveStart("character", -input.value.length);
              return range.text.length - rangeLength;
            }
          },
          value: function() {
            var value =
                $input.length > 0 ? $input.val() : $module.data(metadata.value),
              isEmptyMultiselect =
                $.isArray(value) && value.length === 1 && value[0] === "";
            // prevents placeholder element from being selected when multiple
            return value === undefined || isEmptyMultiselect ? "" : value;
          },
          values: function() {
            var value = module.get.value();
            if (value === "") {
              return "";
            }
            return !module.has.selectInput() && module.is.multiple()
              ? typeof value == "string" // delimited string
                ? value.split(settings.delimiter)
                : ""
              : value;
          },
          remoteValues: function() {
            var values = module.get.values(),
              remoteValues = false;
            if (values) {
              if (typeof values == "string") {
                values = [values];
              }
              $.each(values, function(index, value) {
                var name = module.read.remoteData(value);
                module.verbose(
                  "Restoring value from session data",
                  name,
                  value
                );
                if (name) {
                  if (!remoteValues) {
                    remoteValues = {};
                  }
                  remoteValues[value] = name;
                }
              });
            }
            return remoteValues;
          },
          choiceText: function($choice, preserveHTML) {
            preserveHTML =
              preserveHTML !== undefined ? preserveHTML : settings.preserveHTML;
            if ($choice) {
              if ($choice.find(selector.menu).length > 0) {
                module.verbose("Retrieving text of element with sub-menu");
                $choice = $choice.clone();
                $choice.find(selector.menu).remove();
                $choice.find(selector.menuIcon).remove();
              }
              return $choice.data(metadata.text) !== undefined
                ? $choice.data(metadata.text)
                : preserveHTML
                  ? $.trim($choice.html())
                  : $.trim($choice.text());
            }
          },
          choiceValue: function($choice, choiceText) {
            choiceText = choiceText || module.get.choiceText($choice);
            if (!$choice) {
              return false;
            }
            return $choice.data(metadata.value) !== undefined
              ? String($choice.data(metadata.value))
              : typeof choiceText === "string"
                ? $.trim(choiceText.toLowerCase())
                : String(choiceText);
          },
          inputEvent: function() {
            var input = $search[0];
            if (input) {
              return input.oninput !== undefined
                ? "input"
                : input.onpropertychange !== undefined
                  ? "propertychange"
                  : "keyup";
            }
            return false;
          },
          selectValues: function() {
            var select = {};
            select.values = [];
            $module.find("option").each(function() {
              var $option = $(this),
                name = $option.html(),
                disabled = $option.attr("disabled"),
                value =
                  $option.attr("value") !== undefined
                    ? $option.attr("value")
                    : name;
              if (settings.placeholder === "auto" && value === "") {
                select.placeholder = name;
              } else {
                select.values.push({
                  name: name,
                  value: value,
                  disabled: disabled
                });
              }
            });
            if (settings.placeholder && settings.placeholder !== "auto") {
              module.debug(
                "Setting placeholder value to",
                settings.placeholder
              );
              select.placeholder = settings.placeholder;
            }
            if (settings.sortSelect) {
              select.values.sort(function(a, b) {
                return a.name > b.name ? 1 : -1;
              });
              module.debug("Retrieved and sorted values from select", select);
            } else {
              module.debug("Retrieved values from select", select);
            }
            return select;
          },
          activeItem: function() {
            return $item.filter("." + className.active);
          },
          selectedItem: function() {
            var $selectedItem = $item
              .not(selector.unselectable)
              .filter("." + className.selected);
            return $selectedItem.length > 0 ? $selectedItem : $item.eq(0);
          },
          itemWithAdditions: function(value) {
            var $items = module.get.item(value),
              $userItems = module.create.userChoice(value),
              hasUserItems = $userItems && $userItems.length > 0;
            if (hasUserItems) {
              $items = $items.length > 0 ? $items.add($userItems) : $userItems;
            }
            return $items;
          },
          item: function(value, strict) {
            var $selectedItem = false,
              shouldSearch,
              isMultiple;
            value =
              value !== undefined
                ? value
                : module.get.values() !== undefined
                  ? module.get.values()
                  : module.get.text();
            shouldSearch = isMultiple
              ? value.length > 0
              : value !== undefined && value !== null;
            isMultiple = module.is.multiple() && $.isArray(value);
            strict = value === "" || value === 0 ? true : strict || false;
            if (shouldSearch) {
              $item.each(function() {
                var $choice = $(this),
                  optionText = module.get.choiceText($choice),
                  optionValue = module.get.choiceValue($choice, optionText);
                // safe early exit
                if (optionValue === null || optionValue === undefined) {
                  return;
                }
                if (isMultiple) {
                  if (
                    $.inArray(String(optionValue), value) !== -1 ||
                    $.inArray(optionText, value) !== -1
                  ) {
                    $selectedItem = $selectedItem
                      ? $selectedItem.add($choice)
                      : $choice;
                  }
                } else if (strict) {
                  module.verbose(
                    "Ambiguous dropdown value using strict type check",
                    $choice,
                    value
                  );
                  if (optionValue === value || optionText === value) {
                    $selectedItem = $choice;
                    return true;
                  }
                } else {
                  if (
                    String(optionValue) == String(value) ||
                    optionText == value
                  ) {
                    module.verbose(
                      "Found select item by value",
                      optionValue,
                      value
                    );
                    $selectedItem = $choice;
                    return true;
                  }
                }
              });
            }
            return $selectedItem;
          }
        },

        check: {
          maxSelections: function(selectionCount) {
            if (settings.maxSelections) {
              selectionCount =
                selectionCount !== undefined
                  ? selectionCount
                  : module.get.selectionCount();
              if (selectionCount >= settings.maxSelections) {
                module.debug("Maximum selection count reached");
                if (settings.useLabels) {
                  $item.addClass(className.filtered);
                  module.add.message(message.maxSelections);
                }
                return true;
              } else {
                module.verbose("No longer at maximum selection count");
                module.remove.message();
                module.remove.filteredItem();
                if (module.is.searchSelection()) {
                  module.filterItems();
                }
                return false;
              }
            }
            return true;
          }
        },

        restore: {
          defaults: function() {
            module.clear();
            module.restore.defaultText();
            module.restore.defaultValue();
          },
          defaultText: function() {
            var defaultText = module.get.defaultText(),
              placeholderText = module.get.placeholderText;
            if (defaultText === placeholderText) {
              module.debug("Restoring default placeholder text", defaultText);
              module.set.placeholderText(defaultText);
            } else {
              module.debug("Restoring default text", defaultText);
              module.set.text(defaultText);
            }
          },
          placeholderText: function() {
            module.set.placeholderText();
          },
          defaultValue: function() {
            var defaultValue = module.get.defaultValue();
            if (defaultValue !== undefined) {
              module.debug("Restoring default value", defaultValue);
              if (defaultValue !== "") {
                module.set.value(defaultValue);
                module.set.selected();
              } else {
                module.remove.activeItem();
                module.remove.selectedItem();
              }
            }
          },
          labels: function() {
            if (settings.allowAdditions) {
              if (!settings.useLabels) {
                module.error(error.labels);
                settings.useLabels = true;
              }
              module.debug("Restoring selected values");
              module.create.userLabels();
            }
            module.check.maxSelections();
          },
          selected: function() {
            module.restore.values();
            if (module.is.multiple()) {
              module.debug("Restoring previously selected values and labels");
              module.restore.labels();
            } else {
              module.debug("Restoring previously selected values");
            }
          },
          values: function() {
            // prevents callbacks from occurring on initial load
            module.set.initialLoad();
            if (
              settings.apiSettings &&
              settings.saveRemoteData &&
              module.get.remoteValues()
            ) {
              module.restore.remoteValues();
            } else {
              module.set.selected();
            }
            module.remove.initialLoad();
          },
          remoteValues: function() {
            var values = module.get.remoteValues();
            module.debug("Recreating selected from session data", values);
            if (values) {
              if (module.is.single()) {
                $.each(values, function(value, name) {
                  module.set.text(name);
                });
              } else {
                $.each(values, function(value, name) {
                  module.add.label(value, name);
                });
              }
            }
          }
        },

        read: {
          remoteData: function(value) {
            var name;
            if (window.Storage === undefined) {
              module.error(error.noStorage);
              return;
            }
            name = sessionStorage.getItem(value);
            return name !== undefined ? name : false;
          }
        },

        save: {
          defaults: function() {
            module.save.defaultText();
            module.save.placeholderText();
            module.save.defaultValue();
          },
          defaultValue: function() {
            var value = module.get.value();
            module.verbose("Saving default value as", value);
            $module.data(metadata.defaultValue, value);
          },
          defaultText: function() {
            var text = module.get.text();
            module.verbose("Saving default text as", text);
            $module.data(metadata.defaultText, text);
          },
          placeholderText: function() {
            var text;
            if (
              settings.placeholder !== false &&
              $text.hasClass(className.placeholder)
            ) {
              text = module.get.text();
              module.verbose("Saving placeholder text as", text);
              $module.data(metadata.placeholderText, text);
            }
          },
          remoteData: function(name, value) {
            if (window.Storage === undefined) {
              module.error(error.noStorage);
              return;
            }
            module.verbose(
              "Saving remote data to session storage",
              value,
              name
            );
            sessionStorage.setItem(value, name);
          }
        },

        clear: function() {
          if (module.is.multiple() && settings.useLabels) {
            module.remove.labels();
          } else {
            module.remove.activeItem();
            module.remove.selectedItem();
          }
          module.set.placeholderText();
          module.clearValue();
        },

        clearValue: function() {
          module.set.value("");
        },

        scrollPage: function(direction, $selectedItem) {
          var $currentItem = $selectedItem || module.get.selectedItem(),
            $menu = $currentItem.closest(selector.menu),
            menuHeight = $menu.outerHeight(),
            currentScroll = $menu.scrollTop(),
            itemHeight = $item.eq(0).outerHeight(),
            itemsPerPage = Math.floor(menuHeight / itemHeight),
            maxScroll = $menu.prop("scrollHeight"),
            newScroll =
              direction == "up"
                ? currentScroll - itemHeight * itemsPerPage
                : currentScroll + itemHeight * itemsPerPage,
            $selectableItem = $item.not(selector.unselectable),
            isWithinRange,
            $nextSelectedItem,
            elementIndex;
          elementIndex =
            direction == "up"
              ? $selectableItem.index($currentItem) - itemsPerPage
              : $selectableItem.index($currentItem) + itemsPerPage;
          isWithinRange =
            direction == "up"
              ? elementIndex >= 0
              : elementIndex < $selectableItem.length;
          $nextSelectedItem = isWithinRange
            ? $selectableItem.eq(elementIndex)
            : direction == "up"
              ? $selectableItem.first()
              : $selectableItem.last();
          if ($nextSelectedItem.length > 0) {
            module.debug("Scrolling page", direction, $nextSelectedItem);
            $currentItem.removeClass(className.selected);
            $nextSelectedItem.addClass(className.selected);
            if (settings.selectOnKeydown && module.is.single()) {
              module.set.selectedItem($nextSelectedItem);
            }
            $menu.scrollTop(newScroll);
          }
        },

        set: {
          filtered: function() {
            var isMultiple = module.is.multiple(),
              isSearch = module.is.searchSelection(),
              isSearchMultiple = isMultiple && isSearch,
              searchValue = isSearch ? module.get.query() : "",
              hasSearchValue =
                typeof searchValue === "string" && searchValue.length > 0,
              searchWidth = module.get.searchWidth(),
              valueIsSet = searchValue !== "";
            if (isMultiple && hasSearchValue) {
              module.verbose(
                "Adjusting input width",
                searchWidth,
                settings.glyphWidth
              );
              $search.css("width", searchWidth);
            }
            if (hasSearchValue || (isSearchMultiple && valueIsSet)) {
              module.verbose("Hiding placeholder text");
              $text.addClass(className.filtered);
            } else if (!isMultiple || (isSearchMultiple && !valueIsSet)) {
              module.verbose("Showing placeholder text");
              $text.removeClass(className.filtered);
            }
          },
          empty: function() {
            $module.addClass(className.empty);
          },
          loading: function() {
            $module.addClass(className.loading);
          },
          placeholderText: function(text) {
            text = text || module.get.placeholderText();
            module.debug("Setting placeholder text", text);
            module.set.text(text);
            $text.addClass(className.placeholder);
          },
          tabbable: function() {
            if (module.is.searchSelection()) {
              module.debug("Added tabindex to searchable dropdown");
              $search.val("").attr("tabindex", 0);
              $menu.attr("tabindex", -1);
            } else {
              module.debug("Added tabindex to dropdown");
              if ($module.attr("tabindex") === undefined) {
                $module.attr("tabindex", 0);
                $menu.attr("tabindex", -1);
              }
            }
          },
          initialLoad: function() {
            module.verbose("Setting initial load");
            initialLoad = true;
          },
          activeItem: function($item) {
            if (
              settings.allowAdditions &&
              $item.filter(selector.addition).length > 0
            ) {
              $item.addClass(className.filtered);
            } else {
              $item.addClass(className.active);
            }
          },
          partialSearch: function(text) {
            var length = module.get.query().length;
            $search.val(text.substr(0, length));
          },
          scrollPosition: function($item, forceScroll) {
            var edgeTolerance = 5,
              $menu,
              hasActive,
              offset,
              itemHeight,
              itemOffset,
              menuOffset,
              menuScroll,
              menuHeight,
              abovePage,
              belowPage;

            $item = $item || module.get.selectedItem();
            $menu = $item.closest(selector.menu);
            hasActive = $item && $item.length > 0;
            forceScroll = forceScroll !== undefined ? forceScroll : false;
            if ($item && $menu.length > 0 && hasActive) {
              itemOffset = $item.position().top;

              $menu.addClass(className.loading);
              menuScroll = $menu.scrollTop();
              menuOffset = $menu.offset().top;
              itemOffset = $item.offset().top;
              offset = menuScroll - menuOffset + itemOffset;
              if (!forceScroll) {
                menuHeight = $menu.height();
                belowPage = menuScroll + menuHeight < offset + edgeTolerance;
                abovePage = offset - edgeTolerance < menuScroll;
              }
              module.debug("Scrolling to active item", offset);
              if (forceScroll || abovePage || belowPage) {
                $menu.scrollTop(offset);
              }
              $menu.removeClass(className.loading);
            }
          },
          text: function(text) {
            if (settings.action !== "select") {
              if (settings.action == "combo") {
                module.debug("Changing combo button text", text, $combo);
                if (settings.preserveHTML) {
                  $combo.html(text);
                } else {
                  $combo.text(text);
                }
              } else {
                if (text !== module.get.placeholderText()) {
                  $text.removeClass(className.placeholder);
                }
                module.debug("Changing text", text, $text);
                $text.removeClass(className.filtered);
                if (settings.preserveHTML) {
                  $text.html(text);
                } else {
                  $text.text(text);
                }
              }
            }
          },
          selectedItem: function($item) {
            var value = module.get.choiceValue($item),
              searchText = module.get.choiceText($item, false),
              text = module.get.choiceText($item, true);
            module.debug("Setting user selection to item", $item);
            module.remove.activeItem();
            module.set.partialSearch(searchText);
            module.set.activeItem($item);
            module.set.selected(value, $item);
            module.set.text(text);
          },
          selectedLetter: function(letter) {
            var $selectedItem = $item.filter("." + className.selected),
              alreadySelectedLetter =
                $selectedItem.length > 0 &&
                module.has.firstLetter($selectedItem, letter),
              $nextValue = false,
              $nextItem;
            // check next of same letter
            if (alreadySelectedLetter) {
              $nextItem = $selectedItem.nextAll($item).eq(0);
              if (module.has.firstLetter($nextItem, letter)) {
                $nextValue = $nextItem;
              }
            }
            // check all values
            if (!$nextValue) {
              $item.each(function() {
                if (module.has.firstLetter($(this), letter)) {
                  $nextValue = $(this);
                  return false;
                }
              });
            }
            // set next value
            if ($nextValue) {
              module.verbose("Scrolling to next value with letter", letter);
              module.set.scrollPosition($nextValue);
              $selectedItem.removeClass(className.selected);
              $nextValue.addClass(className.selected);
              if (settings.selectOnKeydown && module.is.single()) {
                module.set.selectedItem($nextValue);
              }
            }
          },
          direction: function($menu) {
            if (settings.direction == "auto") {
              // reset position
              module.remove.upward();

              if (module.can.openDownward($menu)) {
                module.remove.upward($menu);
              } else {
                module.set.upward($menu);
              }
              if (
                !module.is.leftward($menu) &&
                !module.can.openRightward($menu)
              ) {
                module.set.leftward($menu);
              }
            } else if (settings.direction == "upward") {
              module.set.upward($menu);
            }
          },
          upward: function($currentMenu) {
            var $element = $currentMenu || $module;
            $element.addClass(className.upward);
          },
          leftward: function($currentMenu) {
            var $element = $currentMenu || $menu;
            $element.addClass(className.leftward);
          },
          value: function(value, text, $selected) {
            var escapedValue = module.escape.value(value),
              hasInput = $input.length > 0,
              currentValue = module.get.values(),
              stringValue = value !== undefined ? String(value) : value,
              newValue;
            if (hasInput) {
              if (!settings.allowReselection && stringValue == currentValue) {
                module.verbose(
                  "Skipping value update already same value",
                  value,
                  currentValue
                );
                if (!module.is.initialLoad()) {
                  return;
                }
              }

              if (
                module.is.single() &&
                module.has.selectInput() &&
                module.can.extendSelect()
              ) {
                module.debug("Adding user option", value);
                module.add.optionValue(value);
              }
              module.debug("Updating input value", escapedValue, currentValue);
              internalChange = true;
              $input.val(escapedValue);
              if (settings.fireOnInit === false && module.is.initialLoad()) {
                module.debug(
                  "Input native change event ignored on initial load"
                );
              } else {
                module.trigger.change();
              }
              internalChange = false;
            } else {
              module.verbose("Storing value in metadata", escapedValue, $input);
              if (escapedValue !== currentValue) {
                $module.data(metadata.value, stringValue);
              }
            }
            if (settings.fireOnInit === false && module.is.initialLoad()) {
              module.verbose("No callback on initial load", settings.onChange);
            } else {
              settings.onChange.call(element, value, text, $selected);
            }
          },
          active: function() {
            $module.addClass(className.active);
          },
          multiple: function() {
            $module.addClass(className.multiple);
          },
          visible: function() {
            $module.addClass(className.visible);
          },
          exactly: function(value, $selectedItem) {
            module.debug("Setting selected to exact values");
            module.clear();
            module.set.selected(value, $selectedItem);
          },
          selected: function(value, $selectedItem) {
            var isMultiple = module.is.multiple(),
              $userSelectedItem;
            $selectedItem = settings.allowAdditions
              ? $selectedItem || module.get.itemWithAdditions(value)
              : $selectedItem || module.get.item(value);
            if (!$selectedItem) {
              return;
            }
            module.debug("Setting selected menu item to", $selectedItem);
            if (module.is.multiple()) {
              module.remove.searchWidth();
            }
            if (module.is.single()) {
              module.remove.activeItem();
              module.remove.selectedItem();
            } else if (settings.useLabels) {
              module.remove.selectedItem();
            }
            // select each item
            $selectedItem.each(function() {
              var $selected = $(this),
                selectedText = module.get.choiceText($selected),
                selectedValue = module.get.choiceValue($selected, selectedText),
                isFiltered = $selected.hasClass(className.filtered),
                isActive = $selected.hasClass(className.active),
                isUserValue = $selected.hasClass(className.addition),
                shouldAnimate = isMultiple && $selectedItem.length == 1;
              if (isMultiple) {
                if (!isActive || isUserValue) {
                  if (settings.apiSettings && settings.saveRemoteData) {
                    module.save.remoteData(selectedText, selectedValue);
                  }
                  if (settings.useLabels) {
                    module.add.label(
                      selectedValue,
                      selectedText,
                      shouldAnimate
                    );
                    module.add.value(selectedValue, selectedText, $selected);
                    module.set.activeItem($selected);
                    module.filterActive();
                    module.select.nextAvailable($selectedItem);
                  } else {
                    module.add.value(selectedValue, selectedText, $selected);
                    module.set.text(module.add.variables(message.count));
                    module.set.activeItem($selected);
                  }
                } else if (!isFiltered) {
                  module.debug("Selected active value, removing label");
                  module.remove.selected(selectedValue);
                }
              } else {
                if (settings.apiSettings && settings.saveRemoteData) {
                  module.save.remoteData(selectedText, selectedValue);
                }
                module.set.text(selectedText);
                module.set.value(selectedValue, selectedText, $selected);
                $selected
                  .addClass(className.active)
                  .addClass(className.selected);
              }
            });
          }
        },

        add: {
          label: function(value, text, shouldAnimate) {
            var $next = module.is.searchSelection() ? $search : $text,
              escapedValue = module.escape.value(value),
              $label;
            if (settings.ignoreCase) {
              escapedValue = escapedValue.toLowerCase();
            }
            $label = $("<a />")
              .addClass(className.label)
              .attr("data-" + metadata.value, escapedValue)
              .html(templates.label(escapedValue, text));
            $label = settings.onLabelCreate.call($label, escapedValue, text);

            if (module.has.label(value)) {
              module.debug(
                "User selection already exists, skipping",
                escapedValue
              );
              return;
            }
            if (settings.label.variation) {
              $label.addClass(settings.label.variation);
            }
            if (shouldAnimate === true) {
              module.debug("Animating in label", $label);
              $label
                .addClass(className.hidden)
                .insertBefore($next)
                .transition(settings.label.transition, settings.label.duration);
            } else {
              module.debug("Adding selection label", $label);
              $label.insertBefore($next);
            }
          },
          message: function(message) {
            var $message = $menu.children(selector.message),
              html = settings.templates.message(module.add.variables(message));
            if ($message.length > 0) {
              $message.html(html);
            } else {
              $message = $("<div/>")
                .html(html)
                .addClass(className.message)
                .appendTo($menu);
            }
          },
          optionValue: function(value) {
            var escapedValue = module.escape.value(value),
              $option = $input.find(
                'option[value="' + module.escape.string(escapedValue) + '"]'
              ),
              hasOption = $option.length > 0;
            if (hasOption) {
              return;
            }
            // temporarily disconnect observer
            module.disconnect.selectObserver();
            if (module.is.single()) {
              module.verbose("Removing previous user addition");
              $input.find("option." + className.addition).remove();
            }
            $("<option/>")
              .prop("value", escapedValue)
              .addClass(className.addition)
              .html(value)
              .appendTo($input);
            module.verbose("Adding user addition as an <option>", value);
            module.observe.select();
          },
          userSuggestion: function(value) {
            var $addition = $menu.children(selector.addition),
              $existingItem = module.get.item(value),
              alreadyHasValue =
                $existingItem && $existingItem.not(selector.addition).length,
              hasUserSuggestion = $addition.length > 0,
              html;
            if (settings.useLabels && module.has.maxSelections()) {
              return;
            }
            if (value === "" || alreadyHasValue) {
              $addition.remove();
              return;
            }
            if (hasUserSuggestion) {
              $addition
                .data(metadata.value, value)
                .data(metadata.text, value)
                .attr("data-" + metadata.value, value)
                .attr("data-" + metadata.text, value)
                .removeClass(className.filtered);
              if (!settings.hideAdditions) {
                html = settings.templates.addition(
                  module.add.variables(message.addResult, value)
                );
                $addition.html(html);
              }
              module.verbose(
                "Replacing user suggestion with new value",
                $addition
              );
            } else {
              $addition = module.create.userChoice(value);
              $addition.prependTo($menu);
              module.verbose(
                "Adding item choice to menu corresponding with user choice addition",
                $addition
              );
            }
            if (!settings.hideAdditions || module.is.allFiltered()) {
              $addition
                .addClass(className.selected)
                .siblings()
                .removeClass(className.selected);
            }
            module.refreshItems();
          },
          variables: function(message, term) {
            var hasCount = message.search("{count}") !== -1,
              hasMaxCount = message.search("{maxCount}") !== -1,
              hasTerm = message.search("{term}") !== -1,
              values,
              count,
              query;
            module.verbose("Adding templated variables to message", message);
            if (hasCount) {
              count = module.get.selectionCount();
              message = message.replace("{count}", count);
            }
            if (hasMaxCount) {
              count = module.get.selectionCount();
              message = message.replace("{maxCount}", settings.maxSelections);
            }
            if (hasTerm) {
              query = term || module.get.query();
              message = message.replace("{term}", query);
            }
            return message;
          },
          value: function(addedValue, addedText, $selectedItem) {
            var currentValue = module.get.values(),
              newValue;
            if (module.has.value(addedValue)) {
              module.debug("Value already selected");
              return;
            }
            if (addedValue === "") {
              module.debug("Cannot select blank values from multiselect");
              return;
            }
            // extend current array
            if ($.isArray(currentValue)) {
              newValue = currentValue.concat([addedValue]);
              newValue = module.get.uniqueArray(newValue);
            } else {
              newValue = [addedValue];
            }
            // add values
            if (module.has.selectInput()) {
              if (module.can.extendSelect()) {
                module.debug(
                  "Adding value to select",
                  addedValue,
                  newValue,
                  $input
                );
                module.add.optionValue(addedValue);
              }
            } else {
              newValue = newValue.join(settings.delimiter);
              module.debug(
                "Setting hidden input to delimited value",
                newValue,
                $input
              );
            }

            if (settings.fireOnInit === false && module.is.initialLoad()) {
              module.verbose(
                "Skipping onadd callback on initial load",
                settings.onAdd
              );
            } else {
              settings.onAdd.call(
                element,
                addedValue,
                addedText,
                $selectedItem
              );
            }
            module.set.value(newValue, addedValue, addedText, $selectedItem);
            module.check.maxSelections();
          }
        },

        remove: {
          active: function() {
            $module.removeClass(className.active);
          },
          activeLabel: function() {
            $module.find(selector.label).removeClass(className.active);
          },
          empty: function() {
            $module.removeClass(className.empty);
          },
          loading: function() {
            $module.removeClass(className.loading);
          },
          initialLoad: function() {
            initialLoad = false;
          },
          upward: function($currentMenu) {
            var $element = $currentMenu || $module;
            $element.removeClass(className.upward);
          },
          leftward: function($currentMenu) {
            var $element = $currentMenu || $menu;
            $element.removeClass(className.leftward);
          },
          visible: function() {
            $module.removeClass(className.visible);
          },
          activeItem: function() {
            $item.removeClass(className.active);
          },
          filteredItem: function() {
            if (settings.useLabels && module.has.maxSelections()) {
              return;
            }
            if (settings.useLabels && module.is.multiple()) {
              $item.not("." + className.active).removeClass(className.filtered);
            } else {
              $item.removeClass(className.filtered);
            }
            module.remove.empty();
          },
          optionValue: function(value) {
            var escapedValue = module.escape.value(value),
              $option = $input.find(
                'option[value="' + module.escape.string(escapedValue) + '"]'
              ),
              hasOption = $option.length > 0;
            if (!hasOption || !$option.hasClass(className.addition)) {
              return;
            }
            // temporarily disconnect observer
            if (selectObserver) {
              selectObserver.disconnect();
              module.verbose("Temporarily disconnecting mutation observer");
            }
            $option.remove();
            module.verbose(
              "Removing user addition as an <option>",
              escapedValue
            );
            if (selectObserver) {
              selectObserver.observe($input[0], {
                childList: true,
                subtree: true
              });
            }
          },
          message: function() {
            $menu.children(selector.message).remove();
          },
          searchWidth: function() {
            $search.css("width", "");
          },
          searchTerm: function() {
            module.verbose("Cleared search term");
            $search.val("");
            module.set.filtered();
          },
          userAddition: function() {
            $item.filter(selector.addition).remove();
          },
          selected: function(value, $selectedItem) {
            $selectedItem = settings.allowAdditions
              ? $selectedItem || module.get.itemWithAdditions(value)
              : $selectedItem || module.get.item(value);

            if (!$selectedItem) {
              return false;
            }

            $selectedItem.each(function() {
              var $selected = $(this),
                selectedText = module.get.choiceText($selected),
                selectedValue = module.get.choiceValue($selected, selectedText);
              if (module.is.multiple()) {
                if (settings.useLabels) {
                  module.remove.value(selectedValue, selectedText, $selected);
                  module.remove.label(selectedValue);
                } else {
                  module.remove.value(selectedValue, selectedText, $selected);
                  if (module.get.selectionCount() === 0) {
                    module.set.placeholderText();
                  } else {
                    module.set.text(module.add.variables(message.count));
                  }
                }
              } else {
                module.remove.value(selectedValue, selectedText, $selected);
              }
              $selected
                .removeClass(className.filtered)
                .removeClass(className.active);
              if (settings.useLabels) {
                $selected.removeClass(className.selected);
              }
            });
          },
          selectedItem: function() {
            $item.removeClass(className.selected);
          },
          value: function(removedValue, removedText, $removedItem) {
            var values = module.get.values(),
              newValue;
            if (module.has.selectInput()) {
              module.verbose(
                "Input is <select> removing selected option",
                removedValue
              );
              newValue = module.remove.arrayValue(removedValue, values);
              module.remove.optionValue(removedValue);
            } else {
              module.verbose("Removing from delimited values", removedValue);
              newValue = module.remove.arrayValue(removedValue, values);
              newValue = newValue.join(settings.delimiter);
            }
            if (settings.fireOnInit === false && module.is.initialLoad()) {
              module.verbose("No callback on initial load", settings.onRemove);
            } else {
              settings.onRemove.call(
                element,
                removedValue,
                removedText,
                $removedItem
              );
            }
            module.set.value(newValue, removedText, $removedItem);
            module.check.maxSelections();
          },
          arrayValue: function(removedValue, values) {
            if (!$.isArray(values)) {
              values = [values];
            }
            values = $.grep(values, function(value) {
              return removedValue != value;
            });
            module.verbose(
              "Removed value from delimited string",
              removedValue,
              values
            );
            return values;
          },
          label: function(value, shouldAnimate) {
            var $labels = $module.find(selector.label),
              $removedLabel = $labels.filter(
                "[data-" +
                  metadata.value +
                  '="' +
                  module.escape.string(value) +
                  '"]'
              );
            module.verbose("Removing label", $removedLabel);
            $removedLabel.remove();
          },
          activeLabels: function($activeLabels) {
            $activeLabels =
              $activeLabels ||
              $module.find(selector.label).filter("." + className.active);
            module.verbose("Removing active label selections", $activeLabels);
            module.remove.labels($activeLabels);
          },
          labels: function($labels) {
            $labels = $labels || $module.find(selector.label);
            module.verbose("Removing labels", $labels);
            $labels.each(function() {
              var $label = $(this),
                value = $label.data(metadata.value),
                stringValue = value !== undefined ? String(value) : value,
                isUserValue = module.is.userValue(stringValue);
              if (settings.onLabelRemove.call($label, value) === false) {
                module.debug("Label remove callback cancelled removal");
                return;
              }
              module.remove.message();
              if (isUserValue) {
                module.remove.value(stringValue);
                module.remove.label(stringValue);
              } else {
                // selected will also remove label
                module.remove.selected(stringValue);
              }
            });
          },
          tabbable: function() {
            if (module.is.searchSelection()) {
              module.debug("Searchable dropdown initialized");
              $search.removeAttr("tabindex");
              $menu.removeAttr("tabindex");
            } else {
              module.debug("Simple selection dropdown initialized");
              $module.removeAttr("tabindex");
              $menu.removeAttr("tabindex");
            }
          }
        },

        has: {
          menuSearch: function() {
            return module.has.search() && $search.closest($menu).length > 0;
          },
          search: function() {
            return $search.length > 0;
          },
          sizer: function() {
            return $sizer.length > 0;
          },
          selectInput: function() {
            return $input.is("select");
          },
          minCharacters: function(searchTerm) {
            if (settings.minCharacters) {
              searchTerm =
                searchTerm !== undefined
                  ? String(searchTerm)
                  : String(module.get.query());
              return searchTerm.length >= settings.minCharacters;
            }
            return true;
          },
          firstLetter: function($item, letter) {
            var text, firstLetter;
            if (!$item || $item.length === 0 || typeof letter !== "string") {
              return false;
            }
            text = module.get.choiceText($item, false);
            letter = letter.toLowerCase();
            firstLetter = String(text)
              .charAt(0)
              .toLowerCase();
            return letter == firstLetter;
          },
          input: function() {
            return $input.length > 0;
          },
          items: function() {
            return $item.length > 0;
          },
          menu: function() {
            return $menu.length > 0;
          },
          message: function() {
            return $menu.children(selector.message).length !== 0;
          },
          label: function(value) {
            var escapedValue = module.escape.value(value),
              $labels = $module.find(selector.label);
            if (settings.ignoreCase) {
              escapedValue = escapedValue.toLowerCase();
            }
            return (
              $labels.filter(
                "[data-" +
                  metadata.value +
                  '="' +
                  module.escape.string(escapedValue) +
                  '"]'
              ).length > 0
            );
          },
          maxSelections: function() {
            return (
              settings.maxSelections &&
              module.get.selectionCount() >= settings.maxSelections
            );
          },
          allResultsFiltered: function() {
            var $normalResults = $item.not(selector.addition);
            return (
              $normalResults.filter(selector.unselectable).length ===
              $normalResults.length
            );
          },
          userSuggestion: function() {
            return $menu.children(selector.addition).length > 0;
          },
          query: function() {
            return module.get.query() !== "";
          },
          value: function(value) {
            return settings.ignoreCase
              ? module.has.valueIgnoringCase(value)
              : module.has.valueMatchingCase(value);
          },
          valueMatchingCase: function(value) {
            var values = module.get.values(),
              hasValue = $.isArray(values)
                ? values && $.inArray(value, values) !== -1
                : values == value;
            return hasValue ? true : false;
          },
          valueIgnoringCase: function(value) {
            var values = module.get.values(),
              hasValue = false;
            if (!$.isArray(values)) {
              values = [values];
            }
            $.each(values, function(index, existingValue) {
              if (
                String(value).toLowerCase() ==
                String(existingValue).toLowerCase()
              ) {
                hasValue = true;
                return false;
              }
            });
            return hasValue;
          }
        },

        is: {
          active: function() {
            return $module.hasClass(className.active);
          },
          animatingInward: function() {
            return $menu.transition("is inward");
          },
          animatingOutward: function() {
            return $menu.transition("is outward");
          },
          bubbledLabelClick: function(event) {
            return (
              $(event.target).is("select, input") &&
              $module.closest("label").length > 0
            );
          },
          bubbledIconClick: function(event) {
            return $(event.target).closest($icon).length > 0;
          },
          alreadySetup: function() {
            return (
              $module.is("select") &&
              $module.parent(selector.dropdown).data(moduleNamespace) !==
                undefined &&
              $module.prev().length === 0
            );
          },
          animating: function($subMenu) {
            return $subMenu
              ? $subMenu.transition && $subMenu.transition("is animating")
              : $menu.transition && $menu.transition("is animating");
          },
          leftward: function($subMenu) {
            var $selectedMenu = $subMenu || $menu;
            return $selectedMenu.hasClass(className.leftward);
          },
          disabled: function() {
            return $module.hasClass(className.disabled);
          },
          focused: function() {
            return document.activeElement === $module[0];
          },
          focusedOnSearch: function() {
            return document.activeElement === $search[0];
          },
          allFiltered: function() {
            return (
              (module.is.multiple() || module.has.search()) &&
              !(
                settings.hideAdditions == false && module.has.userSuggestion()
              ) &&
              !module.has.message() &&
              module.has.allResultsFiltered()
            );
          },
          hidden: function($subMenu) {
            return !module.is.visible($subMenu);
          },
          initialLoad: function() {
            return initialLoad;
          },
          inObject: function(needle, object) {
            var found = false;
            $.each(object, function(index, property) {
              if (property == needle) {
                found = true;
                return true;
              }
            });
            return found;
          },
          multiple: function() {
            return $module.hasClass(className.multiple);
          },
          remote: function() {
            return settings.apiSettings && module.can.useAPI();
          },
          single: function() {
            return !module.is.multiple();
          },
          selectMutation: function(mutations) {
            var selectChanged = false;
            $.each(mutations, function(index, mutation) {
              if (mutation.target && $(mutation.target).is("select")) {
                selectChanged = true;
                return true;
              }
            });
            return selectChanged;
          },
          search: function() {
            return $module.hasClass(className.search);
          },
          searchSelection: function() {
            return (
              module.has.search() &&
              $search.parent(selector.dropdown).length === 1
            );
          },
          selection: function() {
            return $module.hasClass(className.selection);
          },
          userValue: function(value) {
            return $.inArray(value, module.get.userValues()) !== -1;
          },
          upward: function($menu) {
            var $element = $menu || $module;
            return $element.hasClass(className.upward);
          },
          visible: function($subMenu) {
            return $subMenu
              ? $subMenu.hasClass(className.visible)
              : $menu.hasClass(className.visible);
          },
          verticallyScrollableContext: function() {
            var overflowY =
              $context.get(0) !== window ? $context.css("overflow-y") : false;
            return overflowY == "auto" || overflowY == "scroll";
          },
          horizontallyScrollableContext: function() {
            var overflowX =
              $context.get(0) !== window ? $context.css("overflow-X") : false;
            return overflowX == "auto" || overflowX == "scroll";
          }
        },

        can: {
          activate: function($item) {
            if (settings.useLabels) {
              return true;
            }
            if (!module.has.maxSelections()) {
              return true;
            }
            if (
              module.has.maxSelections() &&
              $item.hasClass(className.active)
            ) {
              return true;
            }
            return false;
          },
          openDownward: function($subMenu) {
            var $currentMenu = $subMenu || $menu,
              canOpenDownward = true,
              onScreen = {},
              calculations;
            $currentMenu.addClass(className.loading);
            calculations = {
              context: {
                offset:
                  $context.get(0) === window
                    ? { top: 0, left: 0 }
                    : $context.offset(),
                scrollTop: $context.scrollTop(),
                height: $context.outerHeight()
              },
              menu: {
                offset: $currentMenu.offset(),
                height: $currentMenu.outerHeight()
              }
            };
            if (module.is.verticallyScrollableContext()) {
              calculations.menu.offset.top += calculations.context.scrollTop;
            }
            onScreen = {
              above:
                calculations.context.scrollTop <=
                calculations.menu.offset.top -
                  calculations.context.offset.top -
                  calculations.menu.height,
              below:
                calculations.context.scrollTop + calculations.context.height >=
                calculations.menu.offset.top -
                  calculations.context.offset.top +
                  calculations.menu.height
            };
            if (onScreen.below) {
              module.verbose("Dropdown can fit in context downward", onScreen);
              canOpenDownward = true;
            } else if (!onScreen.below && !onScreen.above) {
              module.verbose(
                "Dropdown cannot fit in either direction, favoring downward",
                onScreen
              );
              canOpenDownward = true;
            } else {
              module.verbose(
                "Dropdown cannot fit below, opening upward",
                onScreen
              );
              canOpenDownward = false;
            }
            $currentMenu.removeClass(className.loading);
            return canOpenDownward;
          },
          openRightward: function($subMenu) {
            var $currentMenu = $subMenu || $menu,
              canOpenRightward = true,
              isOffscreenRight = false,
              calculations;
            $currentMenu.addClass(className.loading);
            calculations = {
              context: {
                offset:
                  $context.get(0) === window
                    ? { top: 0, left: 0 }
                    : $context.offset(),
                scrollLeft: $context.scrollLeft(),
                width: $context.outerWidth()
              },
              menu: {
                offset: $currentMenu.offset(),
                width: $currentMenu.outerWidth()
              }
            };
            if (module.is.horizontallyScrollableContext()) {
              calculations.menu.offset.left += calculations.context.scrollLeft;
            }
            isOffscreenRight =
              calculations.menu.offset.left -
                calculations.context.offset.left +
                calculations.menu.width >=
              calculations.context.scrollLeft + calculations.context.width;
            if (isOffscreenRight) {
              module.verbose(
                "Dropdown cannot fit in context rightward",
                isOffscreenRight
              );
              canOpenRightward = false;
            }
            $currentMenu.removeClass(className.loading);
            return canOpenRightward;
          },
          click: function() {
            return hasTouch || settings.on == "click";
          },
          extendSelect: function() {
            return settings.allowAdditions || settings.apiSettings;
          },
          show: function() {
            return (
              !module.is.disabled() &&
              (module.has.items() || module.has.message())
            );
          },
          useAPI: function() {
            return $.fn.api !== undefined;
          }
        },

        animate: {
          show: function(callback, $subMenu) {
            var $currentMenu = $subMenu || $menu,
              start = $subMenu
                ? function() {}
                : function() {
                    module.hideSubMenus();
                    module.hideOthers();
                    module.set.active();
                  },
              transition;
            callback = $.isFunction(callback) ? callback : function() {};
            module.verbose("Doing menu show animation", $currentMenu);
            module.set.direction($subMenu);
            transition = module.get.transition($subMenu);
            if (module.is.selection()) {
              module.set.scrollPosition(module.get.selectedItem(), true);
            }
            if (
              module.is.hidden($currentMenu) ||
              module.is.animating($currentMenu)
            ) {
              if (transition == "none") {
                start();
                $currentMenu.transition("show");
                callback.call(element);
              } else if (
                $.fn.transition !== undefined &&
                $module.transition("is supported")
              ) {
                $currentMenu.transition({
                  animation: transition + " in",
                  debug: settings.debug,
                  verbose: settings.verbose,
                  duration: settings.duration,
                  queue: true,
                  onStart: start,
                  onComplete: function() {
                    callback.call(element);
                  }
                });
              } else {
                module.error(error.noTransition, transition);
              }
            }
          },
          hide: function(callback, $subMenu) {
            var $currentMenu = $subMenu || $menu,
              duration = $subMenu ? settings.duration * 0.9 : settings.duration,
              start = $subMenu
                ? function() {}
                : function() {
                    if (module.can.click()) {
                      module.unbind.intent();
                    }
                    module.remove.active();
                  },
              transition = module.get.transition($subMenu);
            callback = $.isFunction(callback) ? callback : function() {};
            if (
              module.is.visible($currentMenu) ||
              module.is.animating($currentMenu)
            ) {
              module.verbose("Doing menu hide animation", $currentMenu);

              if (transition == "none") {
                start();
                $currentMenu.transition("hide");
                callback.call(element);
              } else if (
                $.fn.transition !== undefined &&
                $module.transition("is supported")
              ) {
                $currentMenu.transition({
                  animation: transition + " out",
                  duration: settings.duration,
                  debug: settings.debug,
                  verbose: settings.verbose,
                  queue: false,
                  onStart: start,
                  onComplete: function() {
                    callback.call(element);
                  }
                });
              } else {
                module.error(error.transition);
              }
            }
          }
        },

        hideAndClear: function() {
          module.remove.searchTerm();
          if (module.has.maxSelections()) {
            return;
          }
          if (module.has.search()) {
            module.hide(function() {
              module.remove.filteredItem();
            });
          } else {
            module.hide();
          }
        },

        delay: {
          show: function() {
            module.verbose("Delaying show event to ensure user intent");
            clearTimeout(module.timer);
            module.timer = setTimeout(module.show, settings.delay.show);
          },
          hide: function() {
            module.verbose("Delaying hide event to ensure user intent");
            clearTimeout(module.timer);
            module.timer = setTimeout(module.hide, settings.delay.hide);
          }
        },

        escape: {
          value: function(value) {
            var multipleValues = $.isArray(value),
              stringValue = typeof value === "string",
              isUnparsable = !stringValue && !multipleValues,
              hasQuotes = stringValue && value.search(regExp.quote) !== -1,
              values = [];
            if (isUnparsable || !hasQuotes) {
              return value;
            }
            module.debug("Encoding quote values for use in select", value);
            if (multipleValues) {
              $.each(value, function(index, value) {
                values.push(value.replace(regExp.quote, "&quot;"));
              });
              return values;
            }
            return value.replace(regExp.quote, "&quot;");
          },
          string: function(text) {
            text = String(text);
            return text.replace(regExp.escape, "\\$&");
          }
        },

        setting: function(name, value) {
          module.debug("Changing setting", name, value);
          if ($.isPlainObject(name)) {
            $.extend(true, settings, name);
          } else if (value !== undefined) {
            if ($.isPlainObject(settings[name])) {
              $.extend(true, settings[name], value);
            } else {
              settings[name] = value;
            }
          } else {
            return settings[name];
          }
        },
        internal: function(name, value) {
          if ($.isPlainObject(name)) {
            $.extend(true, module, name);
          } else if (value !== undefined) {
            module[name] = value;
          } else {
            return module[name];
          }
        },
        debug: function() {
          if (!settings.silent && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.debug = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.debug.apply(console, arguments);
            }
          }
        },
        verbose: function() {
          if (!settings.silent && settings.verbose && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.verbose = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.verbose.apply(console, arguments);
            }
          }
        },
        error: function() {
          if (!settings.silent) {
            module.error = Function.prototype.bind.call(
              console.error,
              console,
              settings.name + ":"
            );
            module.error.apply(console, arguments);
          }
        },
        performance: {
          log: function(message) {
            var currentTime, executionTime, previousTime;
            if (settings.performance) {
              currentTime = new Date().getTime();
              previousTime = time || currentTime;
              executionTime = currentTime - previousTime;
              time = currentTime;
              performance.push({
                Name: message[0],
                Arguments: [].slice.call(message, 1) || "",
                Element: element,
                "Execution Time": executionTime
              });
            }
            clearTimeout(module.performance.timer);
            module.performance.timer = setTimeout(
              module.performance.display,
              500
            );
          },
          display: function() {
            var title = settings.name + ":",
              totalTime = 0;
            time = false;
            clearTimeout(module.performance.timer);
            $.each(performance, function(index, data) {
              totalTime += data["Execution Time"];
            });
            title += " " + totalTime + "ms";
            if (moduleSelector) {
              title += " '" + moduleSelector + "'";
            }
            if (
              (console.group !== undefined || console.table !== undefined) &&
              performance.length > 0
            ) {
              console.groupCollapsed(title);
              if (console.table) {
                console.table(performance);
              } else {
                $.each(performance, function(index, data) {
                  console.log(
                    data["Name"] + ": " + data["Execution Time"] + "ms"
                  );
                });
              }
              console.groupEnd();
            }
            performance = [];
          }
        },
        invoke: function(query, passedArguments, context) {
          var object = instance,
            maxDepth,
            found,
            response;
          passedArguments = passedArguments || queryArguments;
          context = element || context;
          if (typeof query == "string" && object !== undefined) {
            query = query.split(/[\. ]/);
            maxDepth = query.length - 1;
            $.each(query, function(depth, value) {
              var camelCaseValue =
                depth != maxDepth
                  ? value +
                    query[depth + 1].charAt(0).toUpperCase() +
                    query[depth + 1].slice(1)
                  : query;
              if (
                $.isPlainObject(object[camelCaseValue]) &&
                depth != maxDepth
              ) {
                object = object[camelCaseValue];
              } else if (object[camelCaseValue] !== undefined) {
                found = object[camelCaseValue];
                return false;
              } else if ($.isPlainObject(object[value]) && depth != maxDepth) {
                object = object[value];
              } else if (object[value] !== undefined) {
                found = object[value];
                return false;
              } else {
                module.error(error.method, query);
                return false;
              }
            });
          }
          if ($.isFunction(found)) {
            response = found.apply(context, passedArguments);
          } else if (found !== undefined) {
            response = found;
          }
          if ($.isArray(returnedValue)) {
            returnedValue.push(response);
          } else if (returnedValue !== undefined) {
            returnedValue = [returnedValue, response];
          } else if (response !== undefined) {
            returnedValue = response;
          }
          return found;
        }
      };

      if (methodInvoked) {
        if (instance === undefined) {
          module.initialize();
        }
        module.invoke(query);
      } else {
        if (instance !== undefined) {
          instance.invoke("destroy");
        }
        module.initialize();
      }
    });
    return returnedValue !== undefined ? returnedValue : $allModules;
  };

  $.fn.dropdown.settings = {
    silent: false,
    debug: false,
    verbose: false,
    performance: true,

    on: "click", // what event should show menu action on item selection
    action: "activate", // action on item selection (nothing, activate, select, combo, hide, function(){})

    values: false, // specify values to use for dropdown

    apiSettings: false,
    selectOnKeydown: true, // Whether selection should occur automatically when keyboard shortcuts used
    minCharacters: 0, // Minimum characters required to trigger API call

    filterRemoteData: false, // Whether API results should be filtered after being returned for query term
    saveRemoteData: true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh

    throttle: 200, // How long to wait after last user input to search remotely

    context: window, // Context to use when determining if on screen
    direction: "auto", // Whether dropdown should always open in one direction
    keepOnScreen: true, // Whether dropdown should check whether it is on screen before showing

    match: "both", // what to match against with search selection (both, text, or label)
    fullTextSearch: false, // search anywhere in value (set to 'exact' to require exact matches)

    placeholder: "auto", // whether to convert blank <select> values to placeholder text
    preserveHTML: true, // preserve html when selecting value
    sortSelect: false, // sort selection on init

    forceSelection: true, // force a choice on blur with search selection

    allowAdditions: false, // whether multiple select should allow user added values
    ignoreCase: false, // whether to consider values not matching in case to be the same
    hideAdditions: true, // whether or not to hide special message prompting a user they can enter a value

    maxSelections: false, // When set to a number limits the number of selections to this count
    useLabels: true, // whether multiple select should filter currently active selections from choices
    delimiter: ",", // when multiselect uses normal <input> the values will be delimited with this character

    showOnFocus: true, // show menu on focus
    allowReselection: false, // whether current value should trigger callbacks when reselected
    allowTab: true, // add tabindex to element
    allowCategorySelection: false, // allow elements with sub-menus to be selected

    fireOnInit: false, // Whether callbacks should fire when initializing dropdown values

    transition: "auto", // auto transition will slide down or up based on direction
    duration: 200, // duration of transition

    glyphWidth: 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width

    // label settings on multi-select
    label: {
      transition: "scale",
      duration: 200,
      variation: false
    },

    // delay before event
    delay: {
      hide: 300,
      show: 200,
      search: 20,
      touch: 50
    },

    /* Callbacks */
    onChange: function(value, text, $selected) {},
    onAdd: function(value, text, $selected) {},
    onRemove: function(value, text, $selected) {},

    onLabelSelect: function($selectedLabels) {},
    onLabelCreate: function(value, text) {
      return $(this);
    },
    onLabelRemove: function(value) {
      return true;
    },
    onNoResults: function(searchTerm) {
      return true;
    },
    onShow: function() {},
    onHide: function() {},

    /* Component */
    name: "Dropdown",
    namespace: "dropdown",

    message: {
      addResult: "Add <b>{term}</b>",
      count: "{count} selected",
      maxSelections: "Max {maxCount} selections",
      noResults: "No results found.",
      serverError: "There was an error contacting the server"
    },

    error: {
      action: "You called a dropdown action that was not defined",
      alreadySetup:
        "Once a select has been initialized behaviors must be called on the created ui dropdown",
      labels: "Allowing user additions currently requires the use of labels.",
      missingMultiple:
        "<select> requires multiple property to be set to correctly preserve multiple values",
      method: "The method you called is not defined.",
      noAPI: "The API module is required to load resources remotely",
      noStorage: "Saving remote data requires session storage",
      noTransition:
        "This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>"
    },

    regExp: {
      escape: /[-[\]{}()*+?.,\\^$|#\s]/g,
      quote: /"/g
    },

    metadata: {
      defaultText: "defaultText",
      defaultValue: "defaultValue",
      placeholderText: "placeholder",
      text: "text",
      value: "value"
    },

    // property names for remote query
    fields: {
      remoteValues: "results", // grouping for api results
      values: "values", // grouping for all dropdown values
      disabled: "disabled", // whether value should be disabled
      name: "name", // displayed dropdown text
      value: "value", // actual dropdown value
      text: "text" // displayed text when selected
    },

    keys: {
      backspace: 8,
      delimiter: 188, // comma
      deleteKey: 46,
      enter: 13,
      escape: 27,
      pageUp: 33,
      pageDown: 34,
      leftArrow: 37,
      upArrow: 38,
      rightArrow: 39,
      downArrow: 40
    },

    selector: {
      addition: ".addition",
      dropdown: ".ui.dropdown",
      hidden: ".hidden",
      icon: "> .dropdown.icon",
      input: '> input[type="hidden"], > select',
      item: ".item",
      label: "> .label",
      remove: "> .label > .delete.icon",
      siblingLabel: ".label",
      menu: ".menu",
      message: ".message",
      menuIcon: ".dropdown.icon",
      search: "input.search, .menu > .search > input, .menu input.search",
      sizer: "> input.sizer",
      text: "> .text:not(.icon)",
      unselectable: ".disabled, .filtered"
    },

    className: {
      active: "active",
      addition: "addition",
      animating: "animating",
      disabled: "disabled",
      empty: "empty",
      dropdown: "ui dropdown",
      filtered: "filtered",
      hidden: "hidden transition",
      item: "item",
      label: "ui label",
      loading: "loading",
      menu: "menu",
      message: "message",
      multiple: "multiple",
      placeholder: "default",
      sizer: "sizer",
      search: "search",
      selected: "selected",
      selection: "selection",
      upward: "upward",
      leftward: "left",
      visible: "visible"
    }
  };

  /* Templates */
  $.fn.dropdown.settings.templates = {
    // generates dropdown from select values
    dropdown: function(select) {
      var placeholder = select.placeholder || false,
        values = select.values || {},
        html = "";
      html += '<i class="dropdown icon"></i>';
      if (select.placeholder) {
        html += '<div class="default text">' + placeholder + "</div>";
      } else {
        html += '<div class="text"></div>';
      }
      html += '<div class="menu">';
      $.each(select.values, function(index, option) {
        html += option.disabled
          ? '<div class="disabled item" data-value="' +
            option.value +
            '">' +
            option.name +
            "</div>"
          : '<div class="item" data-value="' +
            option.value +
            '">' +
            option.name +
            "</div>";
      });
      html += "</div>";
      return html;
    },

    // generates just menu from select
    menu: function(response, fields) {
      var values = response[fields.values] || {},
        html = "";
      $.each(values, function(index, option) {
        var maybeText = option[fields.text]
            ? 'data-text="' + option[fields.text] + '"'
            : "",
          maybeDisabled = option[fields.disabled] ? "disabled " : "";
        html +=
          '<div class="' +
          maybeDisabled +
          'item" data-value="' +
          option[fields.value] +
          '"' +
          maybeText +
          ">";
        html += option[fields.name];
        html += "</div>";
      });
      return html;
    },

    // generates label for multiselect
    label: function(value, text) {
      return text + '<i class="delete icon"></i>';
    },

    // generates messages like "No results"
    message: function(message) {
      return message;
    },

    // generates user addition to selection menu
    addition: function(choice) {
      return choice;
    }
  };
})(jQuery, window, document);
